跳到主要内容

Vue自定义指令、计算属性、监听器、过滤器

自定义指令

官方中文文档 官方文档

当内置指令不满足需求时就需要自定义指令

下面的那个 inserted 是函数类型,一个指令定义对象可以提供如下几个钩子函数

名称作用
bind只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。可以通过比较更新前后的值来忽略不必要的模板更新
componentUpdated指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind只调用一次,指令与元素解绑时调用。

自定义指令的用法

<input type="text" v-函数名称>

全局自定义指令

自定义指令的语法规则(这样创建的是 全局 的)

Vue.directive('函数名称' ,{
inserted: function(el){
// el表示绑定的元素
// 例如获取元素 并设置为焦点
el.focus();
}
})

局部自定义指令

局部指令就是只能在本组件使用的指令

如果想定义一个 局部 的指令可以使用 directives 属性

let vm = new Vue({
el: '#app',
directives: {
函数名称: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
})

带参的自定义指令

例:

Vue.directive('函数名称' ,{
inserted: function(el, binding){
el.style.backgroundColor = binding.value.color;
}
})

指令的用法

<input type="text" v-函数名称='{color: "orange"}'>

这里插入的参数有四个 参考自官方文档

钩子函数的参数 (即 elbindingvnodeoldVnode)。

binding 使用例:

<div v-color='{color: "red"}'></div>

或者交由data的数据管理

<div v-color='msg'></div>
let vm = new Vue({
el: '#app',
data:{
msg: {
color: "red"
}
},
directives: {
color: {
inserted: function (el, binding) {
el.style.backgroundColor = binding.value.color
console.log(binding)
console.log(binding.name)
console.log(binding.value)
}
}
}
})

// 输出为------>
/*
{name: "color", rawName: "v-color", value: {color: "red"}, expression: "{color: "red"}", ...
color
{color: "red"} (这是一个对象)
*/

其他特性

计算属性

计算属性 参考官方文档

为什么需要计算属性? 表达式的计算逻辑可能会比较复杂,使用计算属性可以使模板内容更加简洁

<div id="app">
{{myfunction()}}
{{myfunction02}}
</div>
let vm = new Vue({
el: '#app',
methods: {
myfunction: function(){
return Date.now();
}
},
// 这个计算属性和上面那个methods的用法区别就是
// 上面那个后面要加()因为是调用函数,下面的计算属性则直接使用属性名就好了
computed:{
myfunction02: function(){
// 注意:结果要返回回去,所以需要用 return
return Date.now();
}
}
})

计算属性 和 方法的区别 计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。(例如刷新时,如果是方法则每刷新一次就执行一次,而计算属性只有当数据变更时才继续执行)

如下,当msg更新时就会执行reverseString

<div id="app">
{{msg}}
{{reverseString}}
</div>
let vm = new Vue({
el: '#app',
data: {
msg: 'hello world'
},
computed: {
reverseString: function(){
// 翻转字符串
// 这个翻转字符串的原理就是把字符串切割成数组,
// 然后执行数组的倒序方法,最后把倒序的数组拼接回字符串
return this.msg.split('').reverse().join('');
}
},
})

监听器

应用场景:数据变化执行异步或开销较大的操作

其实也可以用计算属性来做,但是异步和开销教大的操作只能用监听器

注意,监听输入时可以加上一个失去焦点时才执行会效率高一点,否则每敲一个字符就监听一次效率太低了

<div id="app">
<div>
<span>名:</span>
<input type="text" v-model.lazy="firstName">
</div>
<div>
<span>姓:</span>
<input type="text" v-model.lazy="lastName">
</div>
<div>{{fullName}}</div>
</div>
let vm = new Vue({
el: '#app',
data: {
firstName: 'Jim',
lastName: 'Green',
fullName: 'Jim Green'
},
watch: {
// 监听的名字要一样
firstName: function(val){
this.fullName = val + ' ' + this.lastName
},
lastName: function(val){
this.fullName = this.firstName + ' ' + val
}
},
})

监听器验证用户名

<div id="app">
<div>
<span>姓名:</span>
<input type="text" v-model.lazy="uname"> {{tip}}
</div>
</div>
let vm = new Vue({
el: '#app',
data: {
uname: '',
tip: ''
},
methods: {
checkName: function(uname){
let that = this;
// 模拟接口调用
// setTimeout 属于 window 的方法,该方法用于在指定的毫秒数后调用函数或计算表达式。
// 注意,这个方法是不阻塞的
setTimeout(function(){
// 模拟接口调用
if(uname == 'alsritter'){
that.tip = '用户已经存在,请更换一个'
}else{
that.tip = '用户可以使用'
}
},2000)
}
},
watch: {
uname: function(val){
this.checkName(val);
// 添加提示信息,因为上面设置方法有执行延迟
this.tip = '正在验证'
}
},
})

过滤器

官方文档

过滤器应该被添加在 JavaScript 表达式的尾部(实际上花括号里面的内容就是一个js表达式),由“管道”符号表示后面接一个过滤器

格式化数据,比如将字符串格式化为首字母大写,将日期格式转化为指定的格式等

语法:(同自定义指令,这样创建的也是全局的

Vue.filter('过滤器名称', function(value){
// 过滤器业务
})

创建局部的过滤器

let vm = new Vue({
el: '#app',
filters: {
过滤器名称: function(val){

}
}
})

过滤器的使用

<!-- 第一种方式:通过插值表达式 -->
<div>{{msg | upper}}</div>
<!-- 过滤器可以级联的方式使用 -->
<div>{{msg | upper | lower}}</div>

<!-- 第二种方式:属性绑定的值也可以使用过滤器 -->
<div v-bind:id="id | formatId"></div>

例子:首字母大写

<div id="app">
<input type="text"v-model="msg">
<div>{{msg | upper}}</div>
</div>
Vue.filter('upper', function(val){
// 首字母大写
return val.charAt(0).toUpperCase() + val.slice(1)
})
let vm = new Vue({
el: '#app',
data:{
msg: ''
}
})

带参过滤器

语法:

Vue.filter('过滤器名称' , function(value, arg1)){

}

过滤器带参的使用

<div>{{data | format('yyyy-MM-dd')}}</div>

可以看到上面Vue里定义的函数有两个参数(value, arg1),而下面使用时只有一个参数,在Vue中的第一个参数默认是要处理的数据,而第二个开始才是自己创建的参数

例子:使用过滤器格式化日期 将时间格式化成yyyy-MM-dd的格式

<div id="app">
<div>{{date | format('yyyy-MM-dd')}}</div>
</div>
// 对Date的扩展,将 Date 转化为指定格式的String
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
// 例子:
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18

// 使用例
// var time1 = new Date().Format("yyyy-MM-dd");
// var time2 = new Date().Format("yyyy-MM-dd hh:mm:ss");
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}


// Vue 部分
let vm = new Vue({
el: '#app',
data: {
date: new Date()
},
filters: {
format: function (value, arg) {
return new Date().Format(arg);
}
}
})

图书管理

参考自黑马程序--图书管理

数组更新检查

官方文档

操作数组的方法中,分为变异方法非变异方法。其中,变异方法意味着会改变原数组,而非变异方法则只会返回一个新数组。

因为Vue对数据的处理是响应式的,而变异方法对数据不是响应式的

详细的理解:这个要涉及到Vue的双向绑定,Vue的双向绑定的原理是利用了Object.defineProperty 重写了 getter / setter 以达到当属性的值发生变化时来更新视图 但是这样有个缺陷,就是如果不是直接修改的属性,而是调用了变异方法,那么Vue并不知道其值已经发生改变,此时需要手动调用Vue.set以通知Vue。为了解决这个问题Vue对这些变异方法进行了包装 使之用法和以前的数组方法一致

所以 Vue 将 被侦听的数组的变更方法进行了包裹,使这些变异方法也会触发视图更新。 说白了就是 Vue对这些变异方法进行了封装,使之调用也能触发视图更新

参考自 segmentfault--VUE的变异方法

这些被包裹过的方法包括:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

而这些进行了封装的方法就叫 变异方法

还有些 生成新数组的方法

filter()
concat()
slice()

这类方法是返回一个新数组,所以要这样用

this.items = this.items.slice(0,2)

数组的响应式变化

前面提到Vue对一些变异方法进行了封装,但是数组还有一个问题 就是通过索引修改数据也不是响应式的 所以需要 修改响应式数据 通过set方法来修改数组元素

// 参数一表示要处理的数组名称
// 参数二表示要处理的数组的索引
// 参数三表示此处的新值
Vue.set(vm.items,indexOfItem,newValue)
// 或者
vm.$set(vm.items,indexOfItem,newValue)

给对象添加属性

这个问题和上面两个的问题是一样的

就是直接在Vue外面给vm添加对象,这个新添加的属性不是响应式的

let vm = new Vue({
el: '#app',
data: {
info: {
name: 'lisa',
age: 12
}
}
})

// 直接在Vue外面给vm添加对象
vm.into.gender = 'female'

主要原因就是 对象的属性在Vue初始化时就已经确定了,所以后面动态添加的对象不会再添加到响应式里面去 解决办法就是使用set

vm.$set(vm.info,'gender','female')

具体代码

样式

.grid {
margin: auto;
width: 600px;
text-align: center;
}
.grid table {
width: 100%;
/* border-collapse 属性是用来决定表格的边框是分开的还是合并的 */
/* collapse 表示合并的(不过也不会使用 separate 分开,实在太丑了) */
border-collapse: collapse;
}
.grid th,td {
padding: 10px;
border: 1px dashed rgb(84, 90, 60);
height: 35px;
line-height: 35px;
}
.grid th,.add,.total {
padding-top: 5px;
background-color: orange;
}
<div id="app">
<div class="grid">
<div>
<h1>图书管理</h1>
<div class="book">
<div class="add">
<label for="id">编号:</label>
<!-- 当修改时这个输入框禁用,通过v-bind来绑定属性 -->
<input type="text" id="id" v-model="id" :disabled="flag">
<label for="name">名称:</label>
<!-- 自定义指令 -->
<input type="text" id="name" v-model="name" v-focus>
<!-- 监听name属性验证图书是否存在 -->
<button @click='handle' :disabled="submitFlag">提交</button>
</div>
<div class="total">
<span>图书总数:</span>
<span>{{total}}</span>
</div>
</div>
</div>
<table>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in books" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<!-- 过滤器 -->
<td>{{item.date | format('yyyy-MM-dd')}}</td>
<td>
<a href="" @click.prevent="toEdit(item.id)">修改</a>
<span>|</span>
<a href="" @click.prevent="toDelete(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
let vm = new Vue({
el: '#app',
data: {
id: '',
name: '',
flag: false,
submitFlag: true,
books: [{
id: 1,
name: '三国演义',
date: 1595307376196
},
{
id: 2,
name: '水浒传',
date: 1595307376196
},
{
id: 3,
name: '红楼梦',
date: 1595307376196
}, {
id: 4,
name: '西游记',
date: 1595307376196
}]
},
methods: {
handle: function () {
// 因为添加和编辑都要通过这个按钮,所以需要做个分支判断
if (!this.flag) {
// 添加操作
// 添加图书到数组里
let book = {}
book.id = this.id
book.name = this.name
book.date = (new Date).getTime()
this.books.push(book)
} else {
// 编辑操作
// 就是根据当前ID去更新数组中对应的数据
this.books.some((item) => {
if (item.id == this.id) {
item.name = this.name
// 完成更新操作,终止循环
return true
}
})
this.flag = false
}
// 清空表单
this.name = ''
this.id = ''
},
toEdit: function (id) {
// 根据id查询出要编辑的数据
// 通过数组的filter方法来快速找到要查找的数据
let book = this.books.filter(function (item) {
return item.id == id
})
this.flag = true
this.id = book[0].id
this.name = book[0].name
},
toDelete: function (id) {
// 方法一 通过filter方法进行删除
// this.books = this.books.filter(function(item){
// return item.id != id;
// })
console.log((new Date()).getTime());
// 方法二 通过索引删除
let index = this.books.findIndex(function (item) {
return item.id == id
})
// 删除索引处的值
this.books.splice(index, 1)
}
},
filters: {
format: function (value, arg) {
// 格式化日期
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
return new Date(value).Format(arg)
}
},
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}, computed: {
total: function () {
return this.books.length
}
},
watch: {
name: function (val) {
// 监听name属性验证图书是否存在
let flag = this.books.some(function (item) {
return item.name == val
})
if (flag) {
// 图书存在
this.submitFlag = true
} else {
// 图书不存在
this.submitFlag = false
}
}
},
})

问题

Vue 中的 this

参考资料 你不知道的js中关于this绑定机制的解析[看完还不懂算我输] 参考资料 彻底理解js中this的指向

this 的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定 this 到底指向谁,实际上 this 的最终指向的是那个调用它的对象

'use strict'

let obj = {
data:'hello this',
a:function(){
console.log("这是a")
console.log(this)
},
b:{
c:function(fun){
console.log("这是c")
console.log(this)
fun();
}
}
}

let d = function(fun){
console.log("这是d")
console.log(this)
fun();
}

let e = obj.c;

obj.a();
obj.b.c(obj.a);
d(obj.a);

输出的结果为

这是a
{ data: 'hello this', a: [Function: a], b: { c: [Function: c] } }
这是c
{ c: [Function: c] }
这是a
undefined
这是d
undefined
这是a
undefined

箭头函数中的 this

箭头函数中的 this 继承于它 外面第一个不是箭头函数的函数的this指向。 箭头函数的 this 一旦绑定了上下文,就不会被任何代码改变。

例如下面的这个就是指向的 Vue

... 
this.books.some((item) => {
if (item.id == id){
...
}
})

总结就是:箭头函数中的指针实际上和调用它的那个函数的this指针是一样的

UI8tLF.png

无法更新消息

需要把 Vue 丢到最下面才能绑定到 DOM

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<div id="app">
{{message}}
</div>

<script src="/node_modules/vue/dist/vue.min.js"></script>
<script>
let vm = new Vue({
el : "#app",
data : {
message:"helloVue"
}
});
</script>

</body>
</html>

闪烁问题

参考资料 官方v-cloak

因为 Vue 是替换掉原本的 {{value}} 所以在vue加载好之前是会看到这个 {{value}} 占位符的 解决办法,使用css

这个 [ ] 是属性选择器

<style>
[v-cloak]{
display: none;
}
</style>

<div id="app" v-cloak>
{{info.name}}
</div>